Guida Completa e Approfondita: Try, Except, Else, Finally e Oltre
๐ 1. Introduzione Approfondita
1.1 Cosa sono le Eccezioni?
Un'eccezione รจ un evento che si verifica durante l'esecuzione di un programma e che interrompe il normale flusso delle istruzioni.
In Python, quando si verifica un errore durante l'esecuzione, viene "sollevata" (raised) un'eccezione. Se questa eccezione non viene gestita,
il programma si interrompe e viene visualizzato un messaggio di errore chiamato traceback.
Esempio di eccezione non gestita:
# Questo codice genera un errore
numeri = [1, 2, 3]
print(numeri[5]) # IndexError: list index out of range
Traceback (most recent call last):
File "esempio.py", line 2, in <module>
print(numeri[5])
IndexError: list index out of range
1.2 Errori vs Eccezioni
ร importante distinguere tra errori di sintassi ed eccezioni:
โ Errori di Sintassi
Rilevati prima dell'esecuzione
Il programma non puรฒ essere eseguito
Dovuti a codice scritto male
Non possono essere gestiti con try-except
if True # Manca i due puntiprint("Errore")
โ Eccezioni
Si verificano durante l'esecuzione
Codice sintatticamente corretto
Dovute a condizioni impreviste
Possono essere gestite con try-except
if True:
print(10 / 0) # ZeroDivisionError
1.3 Perchรฉ Gestire le Eccezioni?
๐ก Motivazioni Fondamentali
Robustezza: Il programma continua a funzionare anche in presenza di errori
User Experience: Messaggi di errore comprensibili invece di traceback tecnici
Debugging: Logging degli errori per analisi successive
Pulizia Risorse: Garantire la chiusura di file, connessioni database, ecc.
Flusso di Controllo: Gestire scenari alternativi in modo elegante
Sicurezza: Evitare che informazioni sensibili vengano esposte nei messaggi di errore
๐ง 2. Il Blocco Try-Except: Fondamenti
2.1 Sintassi Base e Funzionamento
Il meccanismo try-except permette di "provare" (try) ad eseguire del codice e "catturare" (except) eventuali eccezioni che si verificano.
try:
# Blocco di codice da provare# Se si verifica un'eccezione, l'esecuzione salta al blocco except
codice_rischioso()
except:
# Blocco eseguito solo se si verifica un'eccezione nel try
gestione_errore()
2.2 Flusso di Esecuzione Dettagliato
1. Il programma entra nel blocco TRY
โ
2. Esegue le istruzioni sequenzialmente
โ
3a. Si verifica un'eccezione?
Sร โ Salta immediatamente al blocco EXCEPT
NO โ Continua fino alla fine del TRY
โ
4. Se nessuna eccezione: salta il blocco EXCEPT
โ
5. Continua con il codice successivo
2.3 Esempio Dettagliato Passo-Passo
Analisi del flusso di esecuzione:
print("1. Inizio programma")
try:
print("2. Entrato nel blocco try")
risultato = 10 / 2print(f"3. Risultato: {risultato}")
risultato = 10 / 0# Qui si verifica l'eccezione!print("4. Questa riga NON viene eseguita")
except:
print("5. Gestione errore nel blocco except")
print("6. Fine programma")
1. Inizio programma
2. Entrato nel blocco try
3. Risultato: 5.0
5. Gestione errore nel blocco except
6. Fine programma
Nota Importante: Quando si verifica un'eccezione nel blocco try, l'esecuzione viene immediatamente trasferita
al blocco except. Le istruzioni successive nel try vengono saltate completamente.
๐ฏ 3. Catturare Eccezioni Specifiche
3.1 Perchรฉ Essere Specifici?
โ ๏ธ Problema con Except Generico
Usare except: senza specificare il tipo di eccezione cattura TUTTE le eccezioni, incluse quelle di sistema come KeyboardInterrupt (Ctrl+C). Questo รจ considerato una cattiva pratica perchรฉ:
Nasconde bug nel codice
Rende difficile il debugging
Puรฒ catturare eccezioni che non dovrebbero essere gestite
Impedisce l'interruzione volontaria del programma
3.2 Sintassi per Eccezioni Specifiche
try:
operazione_rischiosa()
except ValueError:
# Gestisce solo ValueErrorprint("Errore di valore")
except TypeError:
# Gestisce solo TypeErrorprint("Errore di tipo")
except (IndexError, KeyError):
# Gestisce sia IndexError che KeyErrorprint("Errore di indice o chiave")
except Exception as e:
# Cattura tutte le altre eccezioniprint(f"Errore generico: {e}")
3.3 Accedere ai Dettagli dell'Eccezione
Usando la sintassi except TipoEccezione as nome_variabile, possiamo accedere all'oggetto eccezione e ottenere informazioni dettagliate:
Accesso completo alle informazioni dell'eccezione:
import sys
try:
numero = int("abc")
except ValueError as e:
print(f"Tipo di eccezione: {type(e).__name__}")
print(f"Messaggio: {e}")
print(f"Argomenti: {e.args}")
# Informazioni sul traceback
exc_type, exc_value, exc_traceback = sys.exc_info()
print(f"Linea: {exc_traceback.tb_lineno}")
Tipo di eccezione: ValueError
Messaggio: invalid literal for int() with base 10: 'abc'
Argomenti: ("invalid literal for int() with base 10: 'abc'",)
Linea: 4
3.4 Gerarchia delle Eccezioni in Python
Le eccezioni in Python sono organizzate in una gerarchia di classi. Quando si cattura un'eccezione, vengono catturate anche tutte le sue sottoclassi:
L'ordine dei blocchi except รจ importante! Le eccezioni piรน specifiche devono essere catturate prima di quelle piรน generali:
# โ CORRETTO: dal piรน specifico al piรน genericotry:
operazione()
except FileNotFoundError: # Specifica
gestisci_file_non_trovato()
except OSError: # Piรน generica
gestisci_errore_os()
except Exception: # Generica
gestisci_errore_generico()
# โ SBAGLIATO: il primo except catturerebbe tuttotry:
operazione()
except Exception: # Troppo presto!
gestisci_errore_generico()
except FileNotFoundError: # Non verrร mai eseguito!
gestisci_file_non_trovato()
3.5 Tabella Completa delle Eccezioni Comuni
Eccezione
Descrizione Dettagliata
Quando si Verifica
Esempio
ValueError
Funzione riceve argomento del tipo giusto ma valore inappropriato
Conversioni fallite, valori fuori range
int("abc"), math.sqrt(-1)
TypeError
Operazione applicata a oggetto di tipo inappropriato
Operazioni tra tipi incompatibili
"5" + 5, len(5)
ZeroDivisionError
Divisione o modulo per zero
Operazioni matematiche con divisore zero
10 / 0, 15 % 0
IndexError
Indice di sequenza fuori range
Accesso a indici non esistenti in liste/tuple
lista[100] con lista di 10 elementi
KeyError
Chiave non trovata in dizionario
Accesso a chiave inesistente
dict["chiave_assente"]
AttributeError
Attributo o metodo non esiste per l'oggetto
Accesso a attributi inesistenti
stringa.append(), numero.upper()
FileNotFoundError
File o directory non trovati
Apertura file inesistente
open("file_inesistente.txt")
PermissionError
Permessi insufficienti per l'operazione
Accesso file/directory senza permessi
open("/root/file.txt", "w")
ImportError
Modulo non trovato o errore nell'import
Import di modulo non installato/esistente
import modulo_inesistente
NameError
Nome variabile non definito
Uso di variabile non dichiarata
print(variabile_non_definita)
MemoryError
Memoria insufficiente
Allocazione troppo grande
lista = [0] * (10**10)
RuntimeError
Errore generico runtime non coperto da altri
Errori non categorizzabili altrimenti
Ricorsione troppo profonda
โจ 4. Il Blocco Else: Esecuzione Condizionale
4.1 Scopo e Funzionamento
Il blocco else viene eseguito solo se NON si sono verificate eccezioni nel blocco try.
Questo รจ utile per separare il codice che potrebbe generare eccezioni dal codice che dovrebbe essere eseguito solo in caso di successo.
TRY: Esegue il codice
โ
Eccezione?
Sร โ EXCEPT | NO โ ELSE
โ
FINALLY: Eseguito sempre
4.2 Vantaggi dell'uso di Else
โ Perchรฉ usare Else invece di mettere tutto nel Try?
Chiarezza: Separa il codice rischioso da quello sicuro
Precisione: Solo il codice che puรฒ generare eccezioni sta nel try
Debugging: Piรน facile identificare la fonte degli errori
Performance: Il blocco try ha un piccolo overhead, meglio tenerlo minimale
4.3 Esempio Comparativo
โ Senza Else (meno chiaro)
try:
file = open("dati.txt")
contenuto = file.read()
# Questo non dovrebbe stare nel try!
elabora_dati(contenuto)
salva_risultati()
invia_notifica()
except FileNotFoundError:
print("File non trovato")
โ Con Else (piรน chiaro)
try:
file = open("dati.txt")
contenuto = file.read()
except FileNotFoundError:
print("File non trovato")
else:
# Eseguito solo se apertura riuscita
elabora_dati(contenuto)
salva_risultati()
invia_notifica()
Il blocco finally รจ il blocco piรน importante per la gestione delle risorse.
Viene SEMPRE eseguito, indipendentemente da:
Se si รจ verificata o meno un'eccezione
Se l'eccezione รจ stata gestita o meno
Se c'รจ un return, break o continue nel try o except
Se viene sollevata una nuova eccezione
๐ก Quando viene eseguito Finally?
# Finally viene SEMPRE eseguito in questi casi:try:
return valore # Finally eseguito prima del return!finally:
cleanup()
try:
break# Finally eseguito prima del break!finally:
cleanup()
try:
raise Exception() # Finally eseguito anche con eccezione non gestita!finally:
cleanup()
5.2 Casi d'Uso Principali
5.2.1 Gestione File
Garanzia di chiusura file:
defprocessa_file(nome_file):
file = Nonetry:
print("1. Apertura file...")
file = open(nome_file, 'r')
print("2. Lettura contenuto...")
contenuto = file.read()
print("3. Elaborazione...")
risultato = elabora(contenuto)
return risultato
except FileNotFoundError:
print("โ File non trovato")
returnNoneexcept PermissionError:
print("โ Permessi insufficienti")
returnNonefinally:
# SEMPRE eseguito, anche se c'รจ return!if file is not None:
print("4. Chiusura file...")
file.close()
print("5. Cleanup completato")
5.2.2 Connessioni Database
Garanzia di chiusura connessione:
defesegui_query(query):
connessione = Nonetry:
connessione = crea_connessione_db()
cursor = connessione.cursor()
cursor.execute(query)
risultati = cursor.fetchall()
connessione.commit()
return risultati
except DatabaseError as e:
if connessione:
connessione.rollback()
print(f"Errore database: {e}")
return []
finally:
# Chiude SEMPRE la connessioneif connessione:
connessione.close()
print("Connessione chiusa")
5.2.3 Lock e Sincronizzazione
Rilascio garantito di lock:
import threading
lock = threading.Lock()
defoperazione_critica():
try:
lock.acquire()
print("Lock acquisito")
# Operazioni sulla risorsa condivisa
risorsa_condivisa.modifica()
except Exception as e:
print(f"Errore: {e}")
finally:
# Lock SEMPRE rilasciato
lock.release()
print("Lock rilasciato")
5.3 Finally vs Context Manager
Python offre i context manager (with statement) come alternativa piรน elegante per la gestione delle risorse:
Con Finally (approccio tradizionale)
file = Nonetry:
file = open("dati.txt")
contenuto = file.read()
print(contenuto)
except FileNotFoundError:
print("File non trovato")
finally:
if file:
file.close()
Con Context Manager (approccio moderno)
try:
withopen("dati.txt") as file:
contenuto = file.read()
print(contenuto)
# File chiuso automaticamente!except FileNotFoundError:
print("File non trovato")
Nota: Il context manager (with) รจ preferibile quando disponibile, ma finally rimane essenziale
per situazioni piรน complesse o quando non esiste un context manager per la risorsa da gestire.
๐ 6. Struttura Completa Try-Except-Else-Finally
6.1 Schema Completo
try:
# 1. Codice che potrebbe generare eccezioni# Mantienilo il piรน breve possibile
operazione_rischiosa()
except SpecificException1 as e:
# 2. Gestione eccezione specifica 1# Eseguito solo se si verifica SpecificException1
gestisci_eccezione1(e)
except SpecificException2 as e:
# 3. Gestione eccezione specifica 2# Eseguito solo se si verifica SpecificException2
gestisci_eccezione2(e)
except (Exception3, Exception4) as e:
# 4. Gestione multipla# Eseguito per Exception3 O Exception4
gestisci_multiple(e)
except Exception as e:
# 5. Catch-all (sempre per ultimo!)# Cattura tutte le altre eccezioni
gestisci_generica(e)
else:
# 6. Eseguito SOLO se NON ci sono eccezioni# Codice da eseguire in caso di successo
operazioni_successo()
finally:
# 7. SEMPRE eseguito# Pulizia risorse, logging, chiusure
cleanup()
6.2 Ordine di Esecuzione: Tutti i Possibili Scenari
A volte vogliamo catturare un'eccezione, eseguire alcune operazioni (come logging), e poi rilanciarla per farla gestire a un livello superiore:
defoperazione_critica():
try:
risultato = operazione_database()
return risultato
except DatabaseError as e:
# Logging dell'errore
logging.error(f"Errore database: {e}")
invia_notifica_admin(e)
# Re-raise: rilancia la stessa eccezioneraise# Nota: nessun argomento!# Ora un altro livello puรฒ gestire l'eccezionetry:
operazione_critica()
except DatabaseError:
print("Gestione a livello superiore")
โ ๏ธ Raise vs Raise e
# โ CORRETTO: mantiene il traceback originaleexcept Exception as e:
log_error(e)
raise# Rilancia con traceback completo# โ SBAGLIATO: perde il traceback originaleexcept Exception as e:
log_error(e)
raise e # Il traceback ricomincia da qui!
7.2 Concatenazione di Eccezioni (Exception Chaining)
Python 3 permette di associare un'eccezione alla sua causa originale usando raise ... from ...:
Exception chaining con from:
classConfigError(Exception):
"""Errore di configurazione dell'applicazione"""passdefcarica_configurazione(file_config):
try:
withopen(file_config) as f:
config = json.load(f)
return config
except FileNotFoundError as e:
# Solleva una nuova eccezione collegata all'originaleraise ConfigError(
f"File di configurazione '{file_config}' non trovato"
) from e
except json.JSONDecodeError as e:
raise ConfigError(
f"Formato JSON non valido: {e.msg} alla riga {e.lineno}"
) from e
# Uso:try:
config = carica_configurazione("config.json")
except ConfigError as e:
print(f"Errore: {e}")
print(f"Causa: {e.__cause__}") # Accesso all'eccezione originale
๐ก Vantaggi dell'Exception Chaining
Mantiene la traccia completa degli errori
Fornisce contesto a diversi livelli di astrazione
Facilita il debugging mostrando la catena di cause
Permette di trasformare eccezioni tecniche in errori piรน comprensibili
7.3 Soppressione del Contesto di Eccezioni
Se vuoi sollevare una nuova eccezione senza mostrare quella originale, usa raise ... from None:
try:
valore = dizionario["chiave"]
except KeyError:
# Solleva un'eccezione piรน user-friendly senza mostrare il KeyErrorraise ValueError("Configurazione mancante") fromNone
7.4 Creazione di Eccezioni Personalizzate
Creare eccezioni personalizzate rende il codice piรน leggibile e permette gestione granulare degli errori:
Sistema completo di eccezioni personalizzate:
# Gerarchia di eccezioni personalizzateclassApplicationError(Exception):
"""Classe base per tutte le eccezioni dell'applicazione"""def__init__(self, message, error_code=None, details=None):
super().__init__(message)
self.message = message
self.error_code = error_code
self.details = details or {}
self.timestamp = datetime.now()
defto_dict(self):
"""Converte l'eccezione in dizionario per logging/API"""return {
'error_type': self.__class__.__name__,
'message': self.message,
'error_code': self.error_code,
'details': self.details,
'timestamp': self.timestamp.isoformat()
}
classValidationError(ApplicationError):
"""Errore di validazione input"""def__init__(self, message, field=None):
super().__init__(message, error_code="VAL_001")
self.field = field
if field:
self.details['field'] = field
classAuthenticationError(ApplicationError):
"""Errore di autenticazione"""def__init__(self, message, user_id=None):
super().__init__(message, error_code="AUTH_001")
if user_id:
self.details['user_id'] = user_id
classResourceNotFoundError(ApplicationError):
"""Risorsa non trovata"""def__init__(self, resource_type, resource_id):
message = f"{resource_type} con ID {resource_id} non trovato"super().__init__(message, error_code="RES_404")
self.details['resource_type'] = resource_type
self.details['resource_id'] = resource_id
classBusinessRuleError(ApplicationError):
"""Violazione regola di business"""def__init__(self, message, rule_name):
super().__init__(message, error_code="BUS_001")
self.details['rule'] = rule_name
# Esempio di utilizzodefcrea_utente(username, email, age):
try:
# Validazioniiflen(username) < 3:
raise ValidationError(
"Username troppo corto",
field="username"
)
if age < 18:
raise BusinessRuleError(
"Etร minima richiesta: 18 anni",
rule_name="minimum_age"
)
if utente_esiste(email):
raise BusinessRuleError(
"Email giร registrata",
rule_name="unique_email"
)
# Creazione utente
utente = salva_utente(username, email, age)
return utente
except ValidationError as e:
logging.warning(f"Validazione fallita: {e.to_dict()}")
raiseexcept BusinessRuleError as e:
logging.info(f"Regola violata: {e.to_dict()}")
raiseexcept ApplicationError as e:
logging.error(f"Errore applicazione: {e.to_dict()}")
raiseexcept Exception as e:
logging.critical(f"Errore inaspettato: {e}")
raise ApplicationError(
"Errore interno del server",
error_code="SYS_500"
) from e
7.5 Context Manager Personalizzato
Puoi creare context manager personalizzati per gestire risorse in modo elegante:
Context manager per gestione timer:
import time
from contextlib import contextmanager
@contextmanagerdeftimer(nome_operazione):
"""Context manager per misurare tempo di esecuzione"""
inizio = time.time()
print(f"โฑ๏ธ Inizio: {nome_operazione}")
try:
yieldexcept Exception as e:
print(f"โ Errore durante {nome_operazione}: {e}")
raisefinally:
fine = time.time()
durata = fine - inizio
print(f"โ Fine: {nome_operazione} (durata: {durata:.2f}s)")
# Uso:with timer("Elaborazione dati"):
# Codice da misurare
processa_file("dati.csv")
analizza_risultati()
7.6 Decoratori per Gestione Errori
Decoratore per retry automatico:
import time
from functools import wraps
defretry(max_tentativi=3, delay=1, eccezioni=(Exception,)):
"""
Decoratore per ritentare una funzione in caso di errore.
Args:
max_tentativi: numero massimo di tentativi
delay: secondi di attesa tra i tentativi
eccezioni: tuple di eccezioni da catturare
"""defdecorator(func):
@wraps(func)
defwrapper(*args, **kwargs):
ultimo_errore = Nonefor tentativo inrange(1, max_tentativi + 1):
try:
risultato = func(*args, **kwargs)
if tentativo > 1:
print(f"โ Successo al tentativo {tentativo}")
return risultato
except eccezioni as e:
ultimo_errore = e
print(f"โ ๏ธ Tentativo {tentativo}/{max_tentativi} fallito: {e}")
if tentativo < max_tentativi:
print(f"โณ Attendo {delay}s prima di ritentare...")
time.sleep(delay)
# Tutti i tentativi fallitiprint(f"โ Tutti i {max_tentativi} tentativi falliti")
raise ultimo_errore
return wrapper
return decorator
# Uso del decoratore:@retry(max_tentativi=5, delay=2, eccezioni=(ConnectionError, TimeoutError))
defconnetti_api(url):
"""Tenta connessione all'API con retry automatico"""
risposta = requests.get(url, timeout=5)
risposta.raise_for_status()
return risposta.json()
# La funzione ritenterร automaticamente in caso di erroretry:
dati = connetti_api("https://api.esempio.com/dati")
except Exception as e:
print(f"Impossibile ottenere dati: {e}")
โ ๏ธ 8. Best Practices e Antipattern
8.1 Best Practices
โ Cosa FARE
Cattura eccezioni specifiche:
# โ BUONOtry:
numero = int(input_utente)
except ValueError:
print("Input non valido")
# โ BUONOif age < 0:
raise ValueError(f"Etร non valida: {age}. Deve essere >= 0")
Documenta le eccezioni sollevate:
# โ BUONOdefleggi_file(percorso):
"""
Legge il contenuto di un file.
Args:
percorso (str): Percorso del file
Returns:
str: Contenuto del file
Raises:
FileNotFoundError: Se il file non esiste
PermissionError: Se mancano i permessi
UnicodeDecodeError: Se il file non รจ UTF-8
"""withopen(percorso, encoding='utf-8') as f:
return f.read()
Logga gli errori appropriatamente:
# โ BUONOimport logging
try:
operazione_critica()
except Exception as e:
logging.exception("Errore in operazione_critica")
# logging.exception include automaticamente il traceback
8.2 Antipattern da Evitare
โ Cosa NON FARE
1. Except Generico Silenzioso (Silent Failure)
# โ PESSIMO - Nasconde tutti gli errori!try:
operazione_rischiosa()
except:
pass# Ignora completamente l'errore# โ MEGLIOtry:
operazione_rischiosa()
except SpecificError as e:
logging.warning(f"Errore previsto: {e}")
# Gestisci appropriatamente
# โ SBAGLIATO - Try troppo ampiotry:
# 100 righe di codice...
operazione1()
operazione2()
operazione3()
# ... altro codiceexcept Exception:
print("Errore") # Ma dove??# โ MEGLIO - Try specificitry:
operazione1()
except Error1:
gestisci_error1()
try:
operazione2()
except Error2:
gestisci_error2()
4. Ritorno di Valori "Magici" invece di Eccezioni
# โ ANTIPATTERNdefdividi(a, b):
if b == 0:
return -1# Valore "magico" per errorereturn a / b
# โ MEGLIOdefdividi(a, b):
if b == 0:
raise ValueError("Divisione per zero")
return a / b
5. Usare Eccezioni per il Flusso di Controllo Normale
# โ SBAGLIATO - Eccezioni non sono goto!try:
for i inrange(100):
if i == 50:
raise StopIteration # NO!
processa(i)
except StopIteration:
pass# โ CORRETTOfor i inrange(50):
processa(i)
6. Perdere Informazioni dell'Eccezione
# โ SBAGLIATOexcept SpecificError:
raise Exception("Errore generico") # Perde dettagli!# โ MEGLIOexcept SpecificError as e:
raise CustomError(f"Dettagli: {e}") from e
๐ฎ 9. Demo Interattiva Avanzata
๐ฒ Simulatore di Scenari di Errore
Clicca per testare diversi scenari di gestione degli errori
๐ 10. Esercizi Pratici Avanzati
๐ Esercizio 1: Sistema di Registrazione Utenti
Obiettivo: Crea una funzione completa di registrazione con validazione e gestione errori.
Requisiti:
Validare username (min 3 caratteri, alfanumerico)
Validare email (formato corretto)
Validare password (min 8 caratteri, almeno 1 numero e 1 maiuscola)
Verificare che email non sia giร registrata
Gestire errori di connessione database
Logging di tutti gli eventi
Usare eccezioni personalizzate
๐ Esercizio 2: Parser di File CSV con Gestione Errori
Obiettivo: Implementa un parser robusto per file CSV.
Requisiti:
Gestire file non trovato, permessi negati, encoding errato
Validare struttura CSV (numero colonne, tipi di dati)
Gestire righe corrotte senza interrompere il parsing
Implementare retry per errori di I/O temporanei
Generare report degli errori trovati
Garantire chiusura file anche in caso di errori
๐ Esercizio 3: Gestore di Transazioni Bancarie
Obiettivo: Sistema di trasferimento denaro con gestione errori completa.
Requisiti:
Validare importi (positivi, formato corretto)
Verificare saldo sufficiente
Gestire lock per evitare race conditions
Implementare rollback in caso di errore
Logging dettagliato di ogni operazione
Notifiche in caso di operazioni sospette
๐ Esercizio 4: Web Scraper Resiliente
Obiettivo: Scraper che gestisce tutti i possibili errori di rete.
Requisiti:
Gestire timeout, errori di connessione, errori HTTP
Implementare retry con backoff esponenziale
Gestire rate limiting (429 Too Many Requests)
Parser HTML robusto (gestire HTML malformato)
Cache per evitare richieste duplicate
Salvataggio progressivo per non perdere dati
๐ 11. Debugging e Analisi degli Errori
11.1 Ottenere Informazioni Dettagliate
Estrazione completa delle informazioni di errore:
import sys
import traceback
defanalizza_errore_completo():
try:
# Codice che genera errore
risultato = 10 / 0except Exception as e:
# 1. Informazioni base sull'eccezioneprint(f"Tipo: {type(e).__name__}")
print(f"Messaggio: {str(e)}")
print(f"Args: {e.args}")
# 2. Informazioni sul traceback
exc_type, exc_value, exc_traceback = sys.exc_info()
print(f"\nFile: {exc_traceback.tb_frame.f_code.co_filename}")
print(f"Funzione: {exc_traceback.tb_frame.f_code.co_name}")
print(f"Linea: {exc_traceback.tb_lineno}")
# 3. Stack trace formattatoprint("\nStack trace completo:")
print(''.join(traceback.format_tb(exc_traceback)))
# 4. Formato come apparirebbe senza try-exceptprint("\nFormato standard:")
print(''.join(traceback.format_exception(exc_type, exc_value, exc_traceback)))
# 5. Variabili locali al momento dell'erroreprint("\nVariabili locali:")
for var_name, var_value in exc_traceback.tb_frame.f_locals.items():
print(f" {var_name} = {var_value}")
from collections import defaultdict, Counter
from datetime import datetime
import json
classErrorMonitor:
"""Sistema di monitoraggio e analisi errori"""def__init__(self):
self.errori = []
self.contatori = Counter()
self.errori_per_tipo = defaultdict(list)
defregistra_errore(self, eccezione, contesto=None):
"""Registra un'eccezione con contesto"""
tipo_errore = type(eccezione).__name__
record = {
'timestamp': datetime.now().isoformat(),
'tipo': tipo_errore,
'messaggio': str(eccezione),
'contesto': contesto or {}
}
self.errori.append(record)
self.contatori[tipo_errore] += 1
self.errori_per_tipo[tipo_errore].append(record)
defottieni_statistiche(self):
"""Genera statistiche sugli errori"""return {
'totale_errori': len(self.errori),
'errori_per_tipo': dict(self.contatori),
'errore_piu_comune': self.contatori.most_common(1)[0] if self.contatori elseNone,
'ultimo_errore': self.errori[-1] if self.errori elseNone
}
defesporta_report(self, file_path):
"""Esporta report completo in JSON"""
report = {
'statistiche': self.ottieni_statistiche(),
'tutti_errori': self.errori
}
withopen(file_path, 'w') as f:
json.dump(report, f, indent=2)
# Uso del monitor
monitor = ErrorMonitor()
defoperazione_monitorata(dati):
try:
risultato = processa(dati)
return risultato
except Exception as e:
monitor.registra_errore(e, contesto={
'funzione': 'operazione_monitorata',
'dati_input': str(dati)[:100] # Primi 100 caratteri
})
raise# Alla fine del programmaprint(monitor.ottieni_statistiche())
monitor.esporta_report('error_report.json')
๐ก 12. Pattern Avanzati
12.1 Pattern: Nullability e Optional
Gestione valori opzionali invece di eccezioni:
from typing import Optional
deftrova_utente(user_id: int) -> Optional[dict]:
"""
Cerca un utente nel database.
Returns:
dict se trovato, None altrimenti (nessuna eccezione)
"""try:
utente = db.query(f"SELECT * FROM users WHERE id={user_id}")
return utente
except NotFoundError:
returnNone# Invece di sollevare eccezione# Uso con gestione esplicita di None
utente = trova_utente(123)
if utente is not None:
print(f"Trovato: {utente['nome']}")
else:
print("Utente non trovato")
12.2 Pattern: Result Type
Ritornare Success o Error invece di eccezioni:
from dataclasses import dataclass
from typing import Generic, TypeVar, Union
T = TypeVar('T')
E = TypeVar('E')
@dataclassclassSuccess(Generic[T]):
"""Rappresenta un'operazione riuscita"""
value: T
defis_success(self) -> bool:
returnTruedefis_error(self) -> bool:
returnFalse@dataclassclassError(Generic[E]):
"""Rappresenta un errore"""
error: E
defis_success(self) -> bool:
returnFalsedefis_error(self) -> bool:
returnTrue
Result = Union[Success[T], Error[E]]
# Uso del pattern Resultdefdividi(a: float, b: float) -> Result[float, str]:
"""Divisione che ritorna Result invece di sollevare eccezioni"""if b == 0:
return Error("Divisione per zero")
return Success(a / b)
# Uso
risultato = dividi(10, 2)
if risultato.is_success():
print(f"Risultato: {risultato.value}")
else:
print(f"Errore: {risultato.error}")
12.3 Pattern: Circuit Breaker
Circuit breaker per servizi esterni:
from enum import Enum
from datetime import datetime, timedelta
classCircuitState(Enum):
CLOSED = "closed"# Funzionante
OPEN = "open"# Troppi errori, circuito aperto
HALF_OPEN = "half_open"# Test se ripristinatoclassCircuitBreaker:
"""
Circuit breaker per proteggere da servizi difettosi.
Previene cascate di errori.
"""def__init__(self, soglia_errori=5, timeout=60):
self.soglia_errori = soglia_errori
self.timeout = timeout
self.errori = 0
self.stato = CircuitState.CLOSED
self.ultimo_errore = Nonedefcall(self, func, *args, **kwargs):
"""Esegue funzione attraverso il circuit breaker"""if self.stato == CircuitState.OPEN:
if datetime.now() - self.ultimo_errore > timedelta(seconds=self.timeout):
self.stato = CircuitState.HALF_OPEN
print("๐ก Circuit HALF_OPEN: tentativo di ripristino")
else:
raise Exception("Circuit breaker OPEN: servizio non disponibile")
try:
risultato = func(*args, **kwargs)
self._on_success()
return risultato
except Exception as e:
self._on_error()
raisedef_on_success(self):
"""Gestisce successo chiamata"""if self.stato == CircuitState.HALF_OPEN:
print("๐ข Circuit CLOSED: servizio ripristinato")
self.stato = CircuitState.CLOSED
self.errori = 0def_on_error(self):
"""Gestisce errore chiamata"""
self.errori += 1
self.ultimo_errore = datetime.now()
if self.errori >= self.soglia_errori:
self.stato = CircuitState.OPEN
print(f"๐ด Circuit OPEN: troppi errori ({self.errori})")
# Uso
breaker = CircuitBreaker(soglia_errori=3, timeout=30)
defchiama_api_esterna():
return breaker.call(requests.get, "https://api.example.com/data")
๐ 13. Risorse e Riferimenti
๐ Documentazione Ufficiale
Python Docs - Errors and Exceptions: Documentazione ufficiale completa
PEP 8: Guida di stile per il codice Python
PEP 3134: Exception Chaining and Embedded Tracebacks
Python Standard Library: Moduli warnings, logging, traceback
๐ Link Utili
Built-in Exceptions Hierarchy - Python.org
Real Python - Python Exceptions Guide
Effective Python by Brett Slatkin
Clean Code in Python - Mariano Anaya
โ 14. Checklist Finale
โ Verifica le tue Conoscenze
โ Comprendi la differenza tra errori di sintassi ed eccezioni runtime
โ Sai quando usare try-except e quando no
โ Catturi sempre eccezioni specifiche invece di except generico
โ Usi finally per garantire cleanup delle risorse
โ Comprendi il flusso di esecuzione di try-except-else-finally
โ Sai come accedere ai dettagli delle eccezioni
โ Conosci la gerarchia delle eccezioni Python
โ Documenti le eccezioni che le tue funzioni possono sollevare
โ Implementi logging appropriato degli errori
โ Sai quando creare eccezioni personalizzate
โ Comprendi exception chaining e re-raising
โ Eviti gli antipattern comuni
โ Usi context manager quando appropriato
โ Conosci pattern avanzati come Circuit Breaker
โ Sai debuggare eccezioni complesse
๐ 15. Conclusione
La gestione degli errori รจ una competenza fondamentale per ogni programmatore Python. Un codice robusto non solo
funziona quando tutto va bene, ma sa gestire elegantemente le situazioni impreviste, fornendo feedback utile
all'utente e mantenendo l'integritร del sistema.
๐ฏ Punti Chiave da Ricordare
Sii Specifico: Cattura sempre eccezioni specifiche, mai generiche
Fail Fast: Rileva gli errori il prima possibile
Clean Resources: Usa sempre finally o context manager
Log Everything: Registra tutti gli errori significativi
User Friendly: Messaggi di errore chiari per gli utenti
Developer Friendly: Stack trace e dettagli per i developer
Test Errors: Testa anche i casi di errore
Document: Documenta le eccezioni che sollevi
๐ช Prossimi Passi
Per consolidare quanto appreso:
Completa tutti gli esercizi proposti
Rivedi il tuo codice esistente e migliora la gestione errori
Implementa un sistema di logging nella tua applicazione
Crea una gerarchia di eccezioni personalizzate
Studia come gestiscono gli errori progetti open source famosi
Pratica con casi reali: API, database, file I/O
Buon coding e... gestisci gli errori con saggezza! ๐โจ